//
//  DGGAliyunOSSSDK.m
//  AliyunOSSiOS
//
//  Created by 胡中楷 on 2020/7/3.
//

#import "DGGAliyunOSSSDK.h"
#import <AliyunOSSiOS/AliyunOSSiOS.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import "DGGAliyunTokenRequest.h"
#import "NSData+GGContentType.h"

@interface DGGAliyunOSSSDK ()

@property (nonatomic, strong) OSSClient *uploadClient;
@property (nonatomic, strong) DGGAliyunTokenRequest *tokenRequest;
@property (nonatomic, strong) OSSFederationToken *token;
@property (nonatomic, strong) id<OSSCredentialProvider> credential;

@end

@implementation DGGAliyunOSSSDK

- (instancetype)init{
    self = [super init];
    if(self){
        ///默认值
        _randomFolder = NO;
        _bucketName = @"dggtechtest";
        _endpoint = @"https://oss-cn-beijing.aliyuncs.com";
        _callBackUrl = @"https://microuag.dgg188.cn/external/oss/callback";
    }
    return self;
}

- (instancetype)initWithBucketName:(NSString *)bucketName
                          endpoint:(NSString *)endpoint
                           sysCode:(NSString *)sysCode
                            sysKey:(NSString *)sysKey{
    self = [super init];
    if(self){
        NSAssert(!(bucketName == nil || [@"" isEqualToString:bucketName]), @"bucketName 不能为空");
        NSAssert(!(endpoint == nil || [@"" isEqualToString:endpoint]), @"endpoint 不能为空");
        NSAssert(!(sysCode == nil || [@"" isEqualToString:sysCode]), @"sysCode 不能为空");
        NSAssert(!(sysKey == nil || [@"" isEqualToString:sysKey]), @"sysKey 不能为空");
        _bucketName = bucketName;
        _endpoint = endpoint;
        _sysCode = sysCode;
        _sysKey = sysKey;
    }
    return self;
}

#pragma mark - 内部方法
- (NSString *)uniqueStr:(NSString *)fileName folderName:(NSString *)folderName{
    if(self.randomFolder){
        NSDate *date = [NSDate date];
        NSDateFormatter *fm = [[NSDateFormatter alloc] init];
        fm.dateFormat = @"yyyyMMddHHmmssS";
        NSString *ddd = [fm stringFromDate:date];
        NSString *signUniqueStr = [NSString stringWithFormat:@"%@%@",fileName, ddd];
        NSString *signUnique = [OSSUtil base64Md5ForData:[signUniqueStr dataUsingEncoding:NSUTF8StringEncoding]];
        if(folderName && (id)folderName != [NSNull null] && ![@"" isEqualToString:folderName]){
            return [NSString stringWithFormat:@"%@/%@/%@", signUnique, folderName, fileName];
        }else{
            return [NSString stringWithFormat:@"%@/%@", signUnique, fileName];
        }
    }else{
        if(folderName && (id)folderName != [NSNull null] && ![@"" isEqualToString:folderName]){
            return [NSString stringWithFormat:@"%@/%@", folderName, fileName];
        }else{
            return [NSString stringWithFormat:@"%@", fileName];
        }
    }
}

- (NSDictionary *)handleCallBackParamsWithKey:(NSString *)objectKey
                                     fileType:(NSString *)fileType
                                   fileLength:(NSUInteger)fileLength
                                     fileName:(NSString *)fileName
                                       fileId:(NSString *)fileId{
    NSMutableDictionary *fileParams = @{
        @"filename": objectKey,
        @"mimeType": fileType,
        @"size": @(fileLength),
        @"file_original_name": fileName,
        @"sys_code": self.sysCode,
        @"bucketname": self.bucketName,
    }.mutableCopy;
    if(fileId){
        [fileParams setValue:fileId forKey:@"fileuid"];
    }
    NSString *jsonStr = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:fileParams options:(NSJSONWritingPrettyPrinted) error:nil] encoding:NSUTF8StringEncoding];
    NSDictionary *resultDC = @{
        @"callbackUrl": self.callBackUrl,
        @"callbackBodyType": @"application/json",
        @"callbackBody": jsonStr
    };
    return resultDC;
}

- (void)dgg_uploadOSSWithFile:(NSData *)fileData
                     fileName:(NSString *)fileName
                   folderName:(NSString *)folderName
                     fileType:(NSString *)fileType
                       fileId:(NSString *)fileId
                       params:(NSDictionary *)params
                     progress:(OSSNetworkingUploadProgressBlock)progressBlock
            completionHandler:(void (^)(NSDictionary *result, NSError *error))completion{
    if(!fileName){
        if(completion){
            completion(nil, [NSError errorWithDomain:NSCocoaErrorDomain code:-1 userInfo:@{@"msg":@"fileName must not be nil"}]);
        }
        return;
    }
    if(!self.uploadClient){
        self.uploadClient = [[OSSClient alloc] initWithEndpoint:self.endpoint credentialProvider:self.credential];
    }
    NSString *objectKey = [self uniqueStr:fileName folderName:folderName];
    OSSPutObjectRequest *put = [OSSPutObjectRequest new];
    put.callbackParam = [self handleCallBackParamsWithKey:objectKey fileType:fileType fileLength:fileData.length fileName:fileName fileId:fileId];
    put.bucketName = self.bucketName;
    put.objectKey = objectKey;
    put.uploadingData = fileData; // 直接上传NSData
    put.uploadProgress = progressBlock;
    put.contentMd5 = [OSSUtil base64Md5ForData:fileData];//设置MD5校验，防止OSS假的上传成功之后
    OSSTask *putTask = [self.uploadClient putObject:put];
    __weak typeof(self) weakSelf = self;
    [putTask continueWithBlock:^id(OSSTask *task) {
        if (!task.error) {
            OSSLogDebug(@"upload object success!");
            OSSTask *task = nil;
            if(params && [params isKindOfClass:([NSDictionary class])]){
                task = [weakSelf.uploadClient presignPublicURLWithBucketName:weakSelf.bucketName withObjectKey:objectKey withParameters:params];
            }else{
                task = [weakSelf.uploadClient presignPublicURLWithBucketName:weakSelf.bucketName withObjectKey:objectKey];
            }
            if (!task.error) {
                OSSLogDebug(@"objectUrl: %@", task.result);
                NSMutableDictionary *resultDC = @{}.mutableCopy;
                [resultDC setValue:task.result forKey:@"oss_filePath"];
                [resultDC setValue:objectKey forKey:@"oss_key"];
                [resultDC setValue:fileName forKey:@"oss_fileName"];
                if(completion){
                    completion(resultDC, task.error);
                }
            } else {
                OSSLogDebug(@"error: %@", task.error);
                if(completion){
                    completion(nil, task.error);
                }
            }
        } else {
            OSSLogDebug(@"upload object failed, error: %@" , task.error);
            if(completion){
                completion(nil, task.error);
            }
        }
        return nil;
    }];
}

#pragma mark - 普通上传
- (void)dgg_uploadOSSWithFile:(NSData *)fileData
                     fileName:(NSString *)fileName
                   folderName:(NSString *)folderName
                     progress:(OSSNetworkingUploadProgressBlock)progressBlock
            completionHandler:(void(^)(NSDictionary *result, NSError *error))completion{
    [self dgg_uploadOSSWithFile:fileData
                       fileName:fileName
                     folderName:folderName
                         fileId:nil
                         params:@{}
                       progress:progressBlock
              completionHandler:completion];
}

#pragma mark - 普通上传 - 带参数
- (void)dgg_uploadOSSWithFile:(NSData *)fileData
                     fileName:(NSString *)fileName
                   folderName:(NSString *)folderName
                       fileId:(NSString *)fileId
                       params:(NSDictionary *)params
                     progress:(OSSNetworkingUploadProgressBlock)progressBlock
            completionHandler:(void (^)(NSDictionary *result, NSError *error))completion{
    //这里获取文件类型
    NSString *fileType = [NSData dgg_imageFormatStringForImageData:fileData];
    [self dgg_uploadOSSWithFile:fileData
                       fileName:fileName
                     folderName:folderName
                       fileType:fileType
                         fileId:fileId
                         params:params
                       progress:progressBlock
              completionHandler:completion];
}

#pragma mark - 普通上传文件，本地地址
- (void)dgg_uploadOSSWithFileURL:(NSString *)filePath
                        fileName:(NSString *)fileName
                      folderName:(NSString *)folderName
                        progress:(OSSNetworkingUploadProgressBlock)progressBlock
               completionHandler:(void(^)(NSDictionary *result, NSError *error))completion{
    [self dgg_uploadOSSWithFileURL:filePath
                          fileName:fileName
                        folderName:folderName
                            fileId:nil
                            params:@{}
                          progress:progressBlock
                 completionHandler:completion];
}

#pragma mark - 普通上传文件，本地地址 - 带参数
- (void)dgg_uploadOSSWithFileURL:(NSString *)filePath
                        fileName:(NSString *)fileName
                      folderName:(NSString *)folderName
                          fileId:(NSString *)fileId
                          params:(NSDictionary *)params
                        progress:(OSSNetworkingUploadProgressBlock)progressBlock
               completionHandler:(void (^)(NSDictionary *result, NSError *error))completion{
    if(filePath){
        NSData *data = [NSData dataWithContentsOfFile:filePath];
        NSString *realFileName = [self handleFileName:fileName filePath:filePath];
        [self dgg_uploadOSSWithFile:data
                           fileName:realFileName
                         folderName:folderName
                             fileId: fileId
                             params:params
                           progress:progressBlock
                  completionHandler:completion];
    }
}

#pragma mark - 分片上传
- (void)dgg_multipartUploadOSSWithFileURL:(NSString *)filePath
                               chuckCount:(NSInteger)chuckCount
                                 fileName:(NSString *)fileName
                               folderName:(NSString *)folderName
                                 fileType:(NSString *)fileType
                                   fileId:(NSString *)fileId
                                 progress:(OSSNetworkingUploadProgressBlock)progressBlock
                        completionHandler:(void(^)(NSDictionary *result, NSError *error))completion{
    __block NSString * uploadId = nil;
    __block NSMutableArray * partInfos = [NSMutableArray new];
    NSString *uploadToBucket = self.bucketName;
    NSString *uploadObjectkey = [self uniqueStr:fileName folderName:folderName];
    OSSInitMultipartUploadRequest *init = [OSSInitMultipartUploadRequest new];
    init.bucketName = uploadToBucket;
    init.objectKey = uploadObjectkey;
    // init.contentType = @"application/octet-stream";
    // multipartUploadInit返回的结果中包含UploadId，UploadId是分片上传的唯一标识。
    if(!self.uploadClient){
        self.uploadClient = [[OSSClient alloc] initWithEndpoint:self.endpoint credentialProvider:self.credential];
    }
    OSSTask *initTask = [self.uploadClient multipartUploadInit:init];
    [initTask waitUntilFinished];
    if (!initTask.error) {
        OSSInitMultipartUploadResult *result = initTask.result;
        uploadId = result.uploadId;
    } else {
        OSSLogDebug(@"multipart upload failed, error: %@", initTask.error);
        if(completion){
            completion(nil, initTask.error);
        }
        return;
    }
    //int chuckCount = 2;// 设置分片上传数量。
    uint64_t fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil] fileSize];//获取文件大小
    uint64_t offset = fileSize / chuckCount;
    if(offset < (1024 * 100) && chuckCount > 1){
        OSSLogDebug(@"设置的分片大小最低不能小于100K");
        if(completion){
            completion(nil, [NSError errorWithDomain:@"com.aliyun.oss.serverError" code:-400 userInfo:@{
                @"Message":@"Your proposed upload smaller than the minimum allowed size.",
                @"Message_CN":@"设置的分片大小最低不能小于100K"
            }]);
        }
        return;
    }
    for (int i = 1; i <= chuckCount; i++) {
        OSSUploadPartRequest * uploadPart = [OSSUploadPartRequest new];
        uploadPart.bucketName = uploadToBucket;
        uploadPart.objectkey = uploadObjectkey;
        uploadPart.uploadId = uploadId;
        uploadPart.partNumber = i; // part number start from 1
        uploadPart.crcFlag = NO;
        uploadPart.uploadPartProgress = progressBlock;
        NSFileHandle *readHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
        [readHandle seekToFileOffset:offset * (i -1)];
        
        NSData* data = [readHandle readDataOfLength:offset];
        uploadPart.uploadPartData = data;
        uploadPart.contentMd5 = [OSSUtil base64Md5ForData:data];
        OSSTask * uploadPartTask = [self.uploadClient uploadPart:uploadPart];
        //等待上传完成
        [uploadPartTask waitUntilFinished];
        
        if (!uploadPartTask.error) {
            OSSUploadPartResult * result = uploadPartTask.result;
            uint64_t fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:uploadPart.uploadPartFileURL.absoluteString error:nil] fileSize];
            uint64_t crc64OfPart;
            NSScanner *scanner = [NSScanner scannerWithString:result.remoteCRC64ecma];
            [scanner scanUnsignedLongLong:&crc64OfPart];
            OSSPartInfo *partData = [OSSPartInfo partInfoWithPartNum:i eTag:result.eTag size:fileSize crc64:crc64OfPart];
            [partInfos addObject:partData];
        } else {
            OSSLogDebug(@"upload part error: %@", uploadPartTask.error);
            if(completion){
                completion(nil, uploadPartTask.error);
            }
            return;
        }
    }
    OSSCompleteMultipartUploadRequest * complete = [OSSCompleteMultipartUploadRequest new];
    complete.callbackParam = [self handleCallBackParamsWithKey:uploadObjectkey fileType:fileType fileLength:fileSize fileName:fileName fileId:fileId];
    complete.bucketName = uploadToBucket;
    complete.objectKey = uploadObjectkey;
    complete.uploadId = uploadId;
    complete.partInfos = partInfos;
    OSSTask *completeTask = [self.uploadClient completeMultipartUpload:complete];
    [[completeTask continueWithBlock:^id(OSSTask *task) {
        if (!task.error) {
            OSSCompleteMultipartUploadResult *result = task.result;
            OSSLogDebug(@"upload object success!");
            NSMutableDictionary *resultDC = @{}.mutableCopy;
            [resultDC setValue:result.location forKey:@"oss_filePath"];
            [resultDC setValue:uploadObjectkey forKey:@"oss_key"];
            [resultDC setValue:fileName forKey:@"oss_fileName"];
            [resultDC setValue:result.serverReturnJsonString forKey:@"serverReturnJsonString"];
            if(completion){
                completion(resultDC, nil);
            }
        } else {
            OSSLogDebug(@"multipart upload failed, error: %@", task.error);
            if(completion){
                completion(nil, task.error);
            }
        }
        return nil;
    }] waitUntilFinished];
}

- (void)dgg_multipartUploadOSSWithFileURL:(NSString *)filePath
                               chuckCount:(NSInteger)chuckCount
                                 fileName:(NSString *)fileName
                               folderName:(NSString *)folderName
                                   fileId:(NSString *)fileId
                                 progress:(OSSNetworkingUploadProgressBlock)progressBlock
                        completionHandler:(void(^)(NSDictionary *result, NSError *error))completion{
    NSString *fileTye = [self mimeTypeForFileAtPath:filePath];
    [self dgg_multipartUploadOSSWithFileURL:filePath
                                 chuckCount:chuckCount
                                   fileName:fileName
                                 folderName:folderName
                                   fileType:fileTye
                                     fileId:fileId
                                   progress:progressBlock completionHandler:completion];
}

- (void)dgg_multipartUploadOSSWithFileURL:(NSString *)filePath
                                 fileName:(NSString *)fileName
                               folderName:(NSString *)folderName
                                   fileId:(NSString *)fileId
                                 progress:(OSSNetworkingUploadProgressBlock)progressBlock
                        completionHandler:(void (^)(NSDictionary * _Nonnull, NSError * _Nonnull))completion{
    uint64_t fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil] fileSize];//获取文件大小
    uint64_t chuckCount = MAX(fileSize / (1024 * 200), 1);//自动分配时，默认每个分片200K大小
    [self dgg_multipartUploadOSSWithFileURL:filePath
                                 chuckCount:chuckCount
                                   fileName:fileName
                                 folderName:folderName
                                     fileId:fileId
                                   progress:progressBlock
                          completionHandler:completion];
}

#pragma mark - 下载文件
- (void)dgg_downloadOSSWithFileKey:(NSString *)objectKey
                         toFileURL:(NSURL *)fileURL
                          progress:(OSSNetworkingDownloadProgressBlock)progressBlock
                 completionHandler:(void (^)(NSDictionary *result, NSError *error))completion{
    if(!objectKey){
        if(completion){
            completion(nil, [NSError errorWithDomain:NSCocoaErrorDomain code:-1 userInfo:@{@"msg":@"objectKey must not be nil"}]);
        }
        return;
    }
    OSSGetObjectRequest * request = [OSSGetObjectRequest new];
    request.bucketName = self.bucketName;
    request.objectKey = objectKey;
    request.crcFlag = OSSRequestCRCOpen;
    if(!self.uploadClient){
        self.uploadClient = [[OSSClient alloc] initWithEndpoint:self.endpoint credentialProvider:self.credential];
    }
    // 可选字段
    request.downloadProgress = progressBlock;
    if(fileURL){
        request.downloadToFileURL = fileURL;
    }
    OSSTask * getTask = [self.uploadClient getObject:request];
    [[getTask continueWithBlock:^id(OSSTask *task) {
        if (!task.error) {
            OSSLogDebug(@"download object success!");
            OSSGetObjectResult *getResult = task.result;
            OSSLogDebug(@"download result: %@", getResult.downloadedData);
            NSMutableDictionary *resultDC = @{}.mutableCopy;
            [resultDC setValue:getResult.downloadedData forKey:@"oss_downloadedData"];
            [resultDC setValue:getResult.objectMeta forKey:@"oss_objectMeta"];
            [resultDC setValue:fileURL forKey:@"oss_fileURL"];
            [resultDC setValue:fileURL.absoluteString forKey:@"oss_filePath"];
            if(completion){
                completion(resultDC, nil);
            }
        } else {
            OSSLogDebug(@"download object failed, error: %@" ,task.error);
            if(completion){
                completion(nil, task.error);
            }
        }
        return nil;
    }] waitUntilFinished];
}

#pragma mark - 处理文件名为空的情况
- (NSString *)handleFileName:(NSString *)fileName filePath:(NSString *)filePath{
    NSString *result = fileName;
    if(!fileName){
        if(filePath){
            result = filePath.lastPathComponent;
        }
    }
    return result;
}

#pragma mark - 获取文件类型
- (NSString *)mimeTypeForFileAtPath:(NSString *)path{
    if (![[[NSFileManager alloc] init] fileExistsAtPath:path]) {
        return nil;
    }
    CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[path pathExtension], NULL);
    CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType);
    CFRelease(UTI);
    if (!MIMEType) {
        return @"application/octet-stream";
    }
    return (__bridge NSString *)(MIMEType);
}

- (DGGAliyunTokenRequest *)tokenRequest{
    if(!_tokenRequest){
        _tokenRequest = [[DGGAliyunTokenRequest alloc] init];
    }
    _tokenRequest.serverSysKey = _sysKey;
    _tokenRequest.serverSysCode = _sysCode;
    return _tokenRequest;
}

- (id<OSSCredentialProvider>)credential{
    if(!_credential){
        _credential = [[OSSFederationCredentialProvider alloc] initWithFederationTokenGetter:^OSSFederationToken * _Nullable{
            OSSLogDebug(@"从服务器获取token");
            dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
            dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
            __weak typeof(self) weakSelf = self;
            dispatch_async(quene, ^{
                [self.tokenRequest requestAliyunTokenGetWithParams:nil headers:nil completionHandler:^(NSDictionary *result, BOOL success, NSError *error) {
                    if(success){
                        NSString *statusCode = [NSString stringWithFormat:@"%@", result[@"code"]];
                        if([statusCode integerValue] == 200){
                            id resultData = result[@"data"];
                            if(resultData && resultData != [NSNull null] && [resultData isKindOfClass:([NSDictionary class])]){
                                NSString *accessKey = [NSString stringWithFormat:@"%@", resultData[@"AccessKeyId"]];
                                NSString *accessKeySecret = [NSString stringWithFormat:@"%@", resultData[@"AccessKeySecret"]];
                                NSString *securityToken = [NSString stringWithFormat:@"%@", resultData[@"SecurityToken"]];
                                NSString *expiration = [NSString stringWithFormat:@"%@", resultData[@"Expiration"]];
                                OSSFederationToken *token = [[OSSFederationToken alloc] init];
                                token.tAccessKey = accessKey;
                                token.tSecretKey = accessKeySecret;
                                token.tToken = securityToken;
                                token.expirationTimeInGMTFormat = expiration;
                                weakSelf.token = token;
                            }else{
                                OSSLogDebug(@"服务器返回token数据为空");
                            }
                        }else{
                            OSSLogDebug(@"token获取失败");
                        }
                    }else{
                        OSSLogDebug(@"token获取失败");
                    }
                    dispatch_semaphore_signal(semaphore);
                }];
            });
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            return self.token;
        }];
    }
    return _credential;
}

@end
